package org.dromara.system.kernel;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.system.constants.OrderConstants;
import org.dromara.system.domain.PayApp;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.IPayOrderService;
import org.dromara.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * redis 计数核心处理
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class RedisAlgorithmKernel {

    private final RedisTemplate<String, String> redisTemplate;
    private final IPayOrderService orderService;
    private final ISysUserService userService;

    private final Random random = new Random();

    private static final String FAIL_IP_PRE = "failIp:";
    private static final String FAIL_IP_SET = "failIpSet";

    private static final String FAIL_ACCOUNT_PRE = "failAccount:";

    private static final String SUCCESS_IP_PRE = "successIp:";
    private static final String SUCCESS_IP_SET = "successIpSet";

    @Value("${feignNew}")
    public Boolean feignNew;
    @Value("${feignInterval}")
    public Integer feignInterval;

    /**
     * 动态计算是否吃单
     *
     * @param app 应用信息
     * @return 是否吃本单
     */
    public boolean isFeign(PayApp app, BigDecimal usdMoney) {

        if (app == null || app.getRatio() == null || usdMoney == null || app.getRatio().compareTo(BigDecimal.ZERO) <= 0) {
            return false;
        }

        if (app.getRatioMin() != null && app.getRatioMax() != null) {
            if (usdMoney.compareTo(app.getRatioMin()) < 0 || usdMoney.compareTo(app.getRatioMax()) > 0) {
                return false;
            }
        }

        SysUserVo user = userService.selectUserById(app.getAgentId());
        if (!Objects.equals(user.getIsFeign(), 1)) {
            return false;
        }

        Long clientId = app.getAppId();
        double failureRate = app.getRatio().divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).doubleValue();


        // 获取请求次数和失败次数
        int requestCount = getRequestCount(app);
        int failureCount = getFailureCount(app);
        if (failureCount == 0) {
            failureCount = 1;
        }
        log.info("额定概率：" + failureRate);

        // 计算当前失败率，并保留4位小数
        BigDecimal currentFailureRateBD = new BigDecimal(failureCount).divide(new BigDecimal(requestCount + 1), 4, RoundingMode.HALF_UP);
        double currentFailureRate = currentFailureRateBD.doubleValue();

        log.info("当前概率：" + currentFailureRate);

        if (feignNew) {
            String key = OrderConstants.app_feign_key + clientId + ":successCount";
            if (RedisUtils.getAtomicValue(key) < feignInterval) {
                RedisUtils.incrAtomicValue(key);
                return false;
            }
            RedisUtils.deleteObject(key);

            return currentFailureRate <= failureRate;
        } else {
            double randomRate = random.nextDouble();

            log.info("随机概率：" + randomRate);

            // 判断本次请求是否失败
            return currentFailureRate <= failureRate && randomRate <= failureRate;
        }
    }

    /**
     * 获取订单总数量
     *
     * @param app 应用ID
     * @return 数量
     */
    private int getRequestCount(PayApp app) {
        if (app == null) {
            return 0;
        }
        Long appId = app.getAppId();
        String countStr = redisTemplate.opsForValue().get(OrderConstants.app_feign_key + appId + ":requestCount");
        if (countStr == null) {
            //取数据库
            int num = Math.toIntExact(orderService.getRealSuccessNum(appId, app.getRatioTime()));

            redisTemplate.opsForValue().set(OrderConstants.app_feign_key + appId + ":requestCount", String.valueOf(num), 5, TimeUnit.MINUTES);
            return num;
        } else {
            return Integer.parseInt(countStr);
        }
    }

    /**
     * 获取吃单数量
     *
     * @param app 应用信息
     * @return 数量
     */
    private int getFailureCount(PayApp app) {
        if (app == null) {
            return 0;
        }
        Long appId = app.getAppId();

        String countStr = redisTemplate.opsForValue().get(OrderConstants.app_feign_key + appId + ":failureCount");
        if (countStr == null) {
            //取数据库
            int num = Math.toIntExact(orderService.getIsFeignNum(appId, app.getRatioTime()));

            redisTemplate.opsForValue().set(OrderConstants.app_feign_key + appId + ":failureCount", String.valueOf(num), 5, TimeUnit.MINUTES);
            return num;
        } else {
            return Integer.parseInt(countStr);
        }
    }

    /**
     * 删除缓存的吃单数量统计
     *
     * @param appId 应用ID
     */
    public void delFeignCount(Long appId) {
        if (appId == null) {
            return;
        }
        redisTemplate.delete(OrderConstants.app_feign_key + appId + ":requestCount");
        redisTemplate.delete(OrderConstants.app_feign_key + appId + ":failureCount");
    }

    /**
     * 更新成功订单总数
     *
     * @param app 应用
     */
    public void updateSuccessCount(PayApp app) {
        if (app == null) {
            return;
        }
        redisTemplate.opsForValue().set(OrderConstants.app_feign_key + app.getAppId() + ":requestCount", String.valueOf(getRequestCount(app) + 1), 5, TimeUnit.MINUTES);
    }

    /**
     * 增加吃单数量
     *
     * @param app 应用
     */
    public void updateFailureCount(PayApp app) {
        if (app == null) {
            return;
        }
        redisTemplate.opsForValue().set(OrderConstants.app_feign_key + app.getAppId() + ":failureCount", String.valueOf(getFailureCount(app) + 1), 5, TimeUnit.MINUTES);
    }

    /**
     * 设置失败IP到redis
     *
     * @param ip      ip
     * @param orderId 订单ID
     */
    public void setFailIpToRedis(String ip, String orderId) {
        String key = FAIL_IP_PRE + ip + ":" + orderId + ":" + Instant.now().getEpochSecond();

        redisTemplate.opsForZSet().add(FAIL_IP_SET, key, Instant.now().getEpochSecond());
    }

    /**
     * 获取redis中失败的订单key 只有24小时内失败的数据
     *
     * @return List<String>
     */
    private List<String> getFailIps() {
        //删除超过24小时的数据
        long twentyFourHoursAgo = Instant.now().minusSeconds(24 * 60 * 60).getEpochSecond();
        redisTemplate.opsForZSet().removeRangeByScore(FAIL_IP_SET, 0, twentyFourHoursAgo);

        //获取剩下的所有失败记录
        Set<ZSetOperations.TypedTuple<String>> tupleSet = redisTemplate.opsForZSet().rangeWithScores(FAIL_IP_SET, 0, -1);
        if (tupleSet == null) {
            return new ArrayList<>();
        }
        return tupleSet.stream().map(ZSetOperations.TypedTuple::getValue).collect(Collectors.toList());
    }

    /**
     * 获取IP失败次数
     *
     * @param ip ip
     * @return
     */
    public Integer checkIpFailNum(String ip) {
        //获取所有失败记录
        List<String> list = getFailIps();
        //筛选出当前IP的
        return (int) list.stream().filter(tmp -> tmp.contains(":" + ip + ":")).count();
    }

    /**
     * 设置本轮收款账号
     *
     * @param groupId   轮询组ID
     * @param accountId 账号ID
     * @param num       账号使用次数
     */
    public void setLoopAccount(Long groupId, Long accountId, Integer num) {
        String key = OrderConstants.pay_group_loops + groupId;
        redisTemplate.opsForZSet().add(key, accountId.toString(), num);
    }

    /**
     * 删除本轮收款账号记录
     *
     * @param groupId 轮询组ID
     */
    public void rmGroupLoop(Long groupId) {
        String key = OrderConstants.pay_group_loops + groupId;
        redisTemplate.delete(key);
    }

    /**
     * 获取本轮已经收款账号列表
     *
     * @param groupId 轮询组ID
     */
    public Map<Long, Integer> getLoopAccount(Long groupId) {
        String key = OrderConstants.pay_group_loops + groupId;
        //获取剩下的所有失败记录
        Set<ZSetOperations.TypedTuple<String>> tupleSet = redisTemplate.opsForZSet().rangeWithScores(key, 0, -1);
        if (tupleSet == null) {
            return new HashMap<>();
        }

        Map<Long, Integer> map = new HashMap<>();
        tupleSet.forEach(tup -> {
            if (tup.getValue() != null && tup.getScore() != null) {
                map.put(Long.valueOf(tup.getValue()), tup.getScore().intValue());
            }
        });
        return map;
    }

    /**
     * 增加账号失败次数到redis
     *
     * @param accountId 账号ID
     */
    public void addFailAccountNumToRedis(Long accountId) {
        String key = FAIL_ACCOUNT_PRE + accountId;
        redisTemplate.opsForValue().increment(key);
    }

    /**
     * 获取账号失败次数
     *
     * @return List<String>
     */
    public Integer getFailAccountNum(Long accountId) {
        String key = FAIL_ACCOUNT_PRE + accountId;
        String val = redisTemplate.opsForValue().get(key);
        return StringUtils.isEmpty(val) ? 0 : Integer.parseInt(val);
    }

    /**
     * 清空账号失败次数
     *
     * @return List<String>
     */
    public void delFailAccountNum(Long accountId) {
        String key = FAIL_ACCOUNT_PRE + accountId;
        redisTemplate.delete(key);
    }

}

